﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using com.hyrentals.webcommon.configuration;

namespace com.hyrentals.webcommon.extensions
{
    public static class HtmlExtensions
    {
        #region css & js & config && overlay

        private static string ConfigScriptStr = string.Empty;
        private const string CONTEXT_STYLE = "CSS_Style";
        private const string CONTEXT_HEAD_SCRIPT = "JS_HeadScript";
        private const string CONTEXT_TAIL_SCRIPT = "JS_TailScript";
        private const string JS_CONFIG = "JS_CONFIG";
        private const string CONTEXT_OVERLAY = "JS_Overlay";

        public static void Init()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("var CONFIG = {};");
            foreach (ContentConfigParamElement param in ContentMappingConfiguration.Current.ConfigParams)
            {
                sb.Append(string.Format("CONFIG[\"{0}\"] = \"{1}\";", param.Name, param.Value));
            }

            string kissyConfigTagStr = string.Empty;
            List<string> packageStrList = new List<string>();
            foreach (ContentPackageElement package in ContentMappingConfiguration.Current.Packages)
            {
                if (!package.IsClientPackage)
                    continue;

                if (package.Name.ToUpper() == "KISSY")
                {
                    kissyConfigTagStr = string.Format("KISSY.Config.tag = \"{0}\";", package.Tag);
                }
                else
                {
                    packageStrList.Add("{" + string.Format("name:\"{0}\",tag:\"{1}\",path:CONFIG[\"{2}\"]+\"{3}\",charset:\"{4}\"",
                       package.Value, package.Tag, package.CDNServer, package.Path, package.Charset) + "}");
                }
            }
            sb.Append(kissyConfigTagStr);
            string debugStr = WebConfig.CurrentRunModel.HasFlag(RunModel.Debug) ? "true" : "false";
            if (packageStrList.Count > 0)
            {
                sb.Append(string.Concat(
                    "KISSY.config({packages:[",
                    string.Join(",", packageStrList.ToArray()),
                    "],debug:",
                    debugStr,
                    "});"
                    ));
            }

            ConfigScriptStr = string.Concat("<script type=\"text/javascript\">", sb.ToString(), "</script>");
        }

        public static void AddStyle(this HtmlHelper htmlHelper, string styleName)
        {
            ContentDic styleDic = (ContentDic)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_STYLE];
            AddContent(ref styleDic, styleName, ContentType.CSS);
            htmlHelper.ViewContext.HttpContext.Items[CONTEXT_STYLE] = styleDic;
        }

        public static void AddHeadScript(this HtmlHelper htmlHelper, string scriptName)
        {
            ContentDic scriptDic = (ContentDic)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_HEAD_SCRIPT];
            AddContent(ref scriptDic, scriptName, ContentType.Javascript);
            htmlHelper.ViewContext.HttpContext.Items[CONTEXT_HEAD_SCRIPT] = scriptDic;

        }

        public static void AddTailScript(this HtmlHelper htmlHelper, string scriptName)
        {
            ContentDic scriptDic = (ContentDic)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_TAIL_SCRIPT];
            AddContent(ref scriptDic, scriptName, ContentType.Javascript);
            htmlHelper.ViewContext.HttpContext.Items[CONTEXT_TAIL_SCRIPT] = scriptDic;
        }

        public static void AddJSConfig(this HtmlHelper htmlHelper, string configKey, object configValue, bool isSerializedString = false)
        {
            Dictionary<string, JSConfigOjb> configDic = (Dictionary<string, JSConfigOjb>)htmlHelper.ViewContext.HttpContext.Items[JS_CONFIG];
            if (configDic != null)
            {
                if (!configDic.ContainsKey(configKey))
                {
                    configDic.Add(configKey, new JSConfigOjb()
                    {
                        ConfigKey = configKey,
                        ConfigValue = configValue,
                        IsSerializedString = isSerializedString
                    });
                }
            }
            else
            {
                configDic = new Dictionary<string, JSConfigOjb>();
                configDic.Add(configKey, new JSConfigOjb()
                {
                    ConfigKey = configKey,
                    ConfigValue = configValue,
                    IsSerializedString = isSerializedString
                });
            }
            htmlHelper.ViewContext.HttpContext.Items[JS_CONFIG] = configDic;
        }

        public static void AddOverlay(this HtmlHelper htmlHelper, string OverlayId, MvcHtmlString html)
        {
            Dictionary<string, MvcHtmlString> overlayDic = (Dictionary<string, MvcHtmlString>)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_OVERLAY];
            if (overlayDic != null)
            {
                if (!overlayDic.ContainsKey(OverlayId))
                {
                    overlayDic.Add(OverlayId, html);
                }
            }
            else
            {
                overlayDic = new Dictionary<string, MvcHtmlString>();
                overlayDic.Add(OverlayId, html);
            }
            htmlHelper.ViewContext.HttpContext.Items[CONTEXT_OVERLAY] = overlayDic;
        }

        public static MvcHtmlString WriteStyles(this HtmlHelper htmlHelper)
        {
            ContentDic styleDic = (ContentDic)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_STYLE];
            return MvcHtmlString.Create(WriteContent(styleDic, ContentType.CSS));
        }

        public static MvcHtmlString WriteHeadScripts(this HtmlHelper htmlHelper)
        {
            ContentDic scriptDic = (ContentDic)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_HEAD_SCRIPT];
            return MvcHtmlString.Create(WriteContent(scriptDic, ContentType.Javascript));
        }

        public static MvcHtmlString WriteTailScripts(this HtmlHelper htmlHelper)
        {
            ContentDic scriptDic = (ContentDic)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_TAIL_SCRIPT];
            return MvcHtmlString.Create(WriteContent(scriptDic, ContentType.Javascript));
        }

        private static string WriteContent(ContentDic contentDic, ContentType contentType)
        {
            if (contentDic == null)
                return string.Empty;

            StringBuilder sb = new StringBuilder();
            ContentConcatCollection contentConcatCollection = ContentMappingConfiguration.Current.Concat;
            string concatMode = contentConcatCollection.Mode.ToLower();
            int maxFiles = contentConcatCollection.MaxFiles;

            foreach (ContentConcatElement contentConcatElement in ContentMappingConfiguration.Current.Concat)
            {
                ContentDicItem contentDicItem = contentDic.GetContentsWithPkg(contentConcatElement.Pkg);
                if (contentDicItem == null)
                    continue;

                List<string> contents = contentDicItem.Contents;
                if (contents == null || contents.Count == 0)
                    continue;

                string cdnServer = GetConfigParam(contentDicItem.Package.CDNServer);
                if (cdnServer == null)
                {
                    throw new Exception(string.Format("there is no {0} parameter in cfgParams section in Content.config", contentDicItem.Package.CDNServer));
                }

                if (concatMode == "on")
                {
                    string[] contentArray = contents.ToArray();
                    int contentCount = contentArray.Length;
                    int begin = 0;
                    while (begin < contentCount)
                    {
                        int takeNum = begin + maxFiles <= contentCount ? maxFiles : contentCount - begin;


                        string path = string.Format("{0}{1}{2}/??{3}?t={4}", cdnServer, contentDicItem.Package.Path, contentDicItem.Package.Value, string.Join(",", contentArray.Skip(begin).Take(takeNum)), contentDicItem.Package.Tag);

                        if ((contentType & ContentType.Javascript) != 0)
                        {
                            sb.Append(GenerateScriptTag(path));
                        }
                        else if ((contentType & ContentType.CSS) != 0)
                        {
                            sb.Append(GenerateStyleTag(path));
                        }

                        begin += takeNum;

                    }
                }
                else
                {
                    foreach (string content in contents)
                    {
                        string path = string.Format("{0}{1}{2}/{3}?t={4}", cdnServer, contentDicItem.Package.Path, contentDicItem.Package.Value, content, contentDicItem.Package.Tag);
                        if ((contentType & ContentType.Javascript) != 0)
                        {
                            sb.Append(GenerateScriptTag(path));
                        }
                        else if ((contentType & ContentType.CSS) != 0)
                        {
                            sb.Append(GenerateStyleTag(path));
                        }
                    }
                }
            }
            return sb.ToString();
        }

        public static TagBuilder GenerateStyleTag(string stylePath)
        {
            var builder = new TagBuilder("link");
            builder.Attributes.Add("rel", "stylesheet");
            builder.Attributes.Add("type", "text/css");
            builder.Attributes.Add("href", stylePath);

            return builder;
        }

        public static TagBuilder GenerateScriptTag(string scriptPath)
        {
            var builder = new TagBuilder("script");
            builder.Attributes.Add("type", "text/javascript");
            builder.Attributes.Add("src", scriptPath);

            return builder;
        }

        public static MvcHtmlString WriteConfigScript(this HtmlHelper htmlHelper)
        {
            return MvcHtmlString.Create(ConfigScriptStr);
        }

        public static MvcHtmlString WriteJSConfigs(this HtmlHelper htmlHelper)
        {
            StringBuilder sb = new StringBuilder();
            Dictionary<string, JSConfigOjb> configDic = (Dictionary<string, JSConfigOjb>)htmlHelper.ViewContext.HttpContext.Items[JS_CONFIG];
            if (configDic != null)
            {
                foreach (KeyValuePair<string, JSConfigOjb> config in configDic)
                {
                    if (config.Value.IsSerializedString)
                        sb.Append(string.Format("CONFIG[\"{0}\"] = \"{1}\";", config.Key, config.Value.ConfigValue));
                    else
                        sb.Append(string.Format("CONFIG[\"{0}\"] = {1};", config.Key, config.Value.ConfigValue));
                }
            }
            return MvcHtmlString.Create(string.Concat("<script type=\"text/javascript\">", sb.ToString(), "</script>"));
        }

        public static MvcHtmlString WriteOverlays(this HtmlHelper htmlHelper)
        {
            StringBuilder sb = new StringBuilder();
            Dictionary<string, MvcHtmlString> overlayDic = (Dictionary<string, MvcHtmlString>)htmlHelper.ViewContext.HttpContext.Items[CONTEXT_OVERLAY];
            if (overlayDic != null)
            {
                foreach (MvcHtmlString overlayHtml in overlayDic.Values)
                {
                    sb.Append(overlayHtml.ToHtmlString());
                }
            }
            return MvcHtmlString.Create(sb.ToString());
        }

        private static void AddContent(ref ContentDic contentDic, string contentName, ContentType contentType)
        {
            string pkgName, contentPath;
            ParseName(contentName, out pkgName, out contentPath);

            ContentPackageElement packageDetail = GetPackage(pkgName);
            if (packageDetail == null)
            {
                throw new Exception(string.Format("the package {0} does not exist in Content.config", pkgName));
            }

            if (WebConfig.CurrentRunModel.HasFlag(RunModel.Release))
            {
                contentPath += "-min";
            }

            if ((contentType & ContentType.Javascript) != 0)
            {
                contentPath += ".js";
            }
            else if ((contentType & ContentType.CSS) != 0)
            {
                contentPath += ".css";
            }

            if (contentDic != null)
            {
                contentDic.AddContent(pkgName, contentPath);
            }
            else
            {
                contentDic = new ContentDic();
                contentDic.AddContent(pkgName, contentPath);
            }
        }

        private static void ParseName(string name, out string package, out string path)
        {
            package = null;
            path = null;
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("the argument name cannot be null or empty");
            }
            int index = name.IndexOf('/');
            if (index == -1)
            {
                throw new Exception(string.Format("please set the package name in {0}", name));
            }
            package = name.Substring(0, index);
            if (index >= name.Length - 1)
            {
                throw new Exception(string.Format("please set the package path in {0}", name));
            }
            path = name.Substring(index + 1);
        }

        public static string GetConfigParam(string name)
        {
            string value = null;
            foreach (ContentConfigParamElement param in ContentMappingConfiguration.Current.ConfigParams)
            {
                if (param.Name == name)
                {
                    value = param.Value;
                    break;
                }
            }
            return value;
        }

        private static string GetFullPath(string name, ContentType contentType)
        {
            string package, path;
            try
            {
                ParseName(name, out package, out path);

                ContentPackageElement packageDetail = GetPackage(package);
                if (packageDetail == null)
                {
                    throw new Exception(string.Format("the package {0} does not exist in Content.config", package));
                }

                string cdnServer = GetConfigParam(packageDetail.CDNServer);
                if (cdnServer == null)
                {
                    throw new Exception(string.Format("there is no {0} parameter in cfgParams section in Content.config", packageDetail.CDNServer));
                }

                if (WebConfig.CurrentRunModel.HasFlag(RunModel.Release))
                {
                    path += "-min";
                }

                if ((contentType & ContentType.Javascript) != 0)
                {
                    path += ".js";
                }
                else if ((contentType & ContentType.CSS) != 0)
                {
                    path += ".css";
                }


                path += "?t=" + packageDetail.Tag;

                return string.Concat(cdnServer, packageDetail.Path, packageDetail.Value, "/", path);
            }
            catch (Exception)
            {
                //Todo: save exception to log
                return null;
            }
        }

        private static ContentPackageElement GetPackage(string name)
        {
            foreach (ContentPackageElement package in ContentMappingConfiguration.Current.Packages)
            {
                if (package.Name == name)
                {
                    return package;
                }
            }
            return null;
        }

        public static MvcHtmlString AttachStyle(this HtmlHelper htmlHelper, string styleName)
        {
            string styleFullPath = GetFullPath(styleName, ContentType.CSS);
            if (styleFullPath == null)
                return MvcHtmlString.Empty;

            var builder = new TagBuilder("link");
            builder.Attributes.Add("rel", "stylesheet");
            builder.Attributes.Add("type", "text/css");
            builder.Attributes.Add("href", styleFullPath);

            return MvcHtmlString.Create(builder.ToString());
        }

        public static MvcHtmlString AttachScript(this HtmlHelper htmlHelper, string scriptName)
        {
            string scriptFullPath = GetFullPath(scriptName, ContentType.Javascript);
            if (scriptFullPath == null)
                return MvcHtmlString.Empty;

            var builder = new TagBuilder("script");
            builder.Attributes.Add("type", "text/javascript");
            builder.Attributes.Add("src", scriptFullPath);

            return MvcHtmlString.Create(builder.ToString());
        }

        public static MvcHtmlString AttachStyles(this HtmlHelper htmlHelper, string pkgName, params string[] parameters)
        {
            return MvcHtmlString.Create(AttachContent(pkgName, parameters, ContentType.CSS));
        }

        public static MvcHtmlString AttachScripts(this HtmlHelper htmlHelper, string pkgName, params string[] parameters)
        {
            return MvcHtmlString.Create(AttachContent(pkgName, parameters, ContentType.Javascript));
        }

        private static string AttachContent(string pkgName, string[] parameters, ContentType contentType)
        {
            if (parameters == null || parameters.Length == 0)
                return string.Empty;

            ContentPackageElement packageDetail = GetPackage(pkgName);
            if (packageDetail == null)
            {
                throw new Exception(string.Format("the package {0} does not exist in Content.config", pkgName));
            }

            string cdnServer = GetConfigParam(packageDetail.CDNServer);
            if (cdnServer == null)
            {
                throw new Exception(string.Format("there is no {0} parameter in cfgParams section in Content.config", packageDetail.CDNServer));
            }

            StringBuilder sb = new StringBuilder();
            ContentConcatCollection contentConcatCollection = ContentMappingConfiguration.Current.Concat;
            string concatMode = contentConcatCollection.Mode.ToLower();
            int maxFiles = contentConcatCollection.MaxFiles;
            bool isRelease = WebConfig.CurrentRunModel.HasFlag(RunModel.Release);

            if (isRelease)
            {
                for (int index = 0; index < parameters.Length; ++index)
                {
                    parameters[index] += "-min";
                }
            }

            if ((contentType & ContentType.Javascript) != 0)
            {
                for (int index = 0; index < parameters.Length; ++index)
                {
                    parameters[index] += ".js";
                }
            }
            else if ((contentType & ContentType.CSS) != 0)
            {
                for (int index = 0; index < parameters.Length; ++index)
                {
                    parameters[index] += ".css";
                }
            }


            if (concatMode == "on")
            {
                string[] contentArray = parameters;
                int contentCount = contentArray.Length;
                int begin = 0;
                while (begin < contentCount)
                {
                    int takeNum = begin + maxFiles <= contentCount ? maxFiles : contentCount - begin;


                    string path = string.Format("{0}{1}{2}/??{3}?t={4}", cdnServer, packageDetail.Path, packageDetail.Value, string.Join(",", contentArray.Skip(begin).Take(takeNum)), packageDetail.Tag);

                    if ((contentType & ContentType.Javascript) != 0)
                    {
                        sb.Append(GenerateScriptTag(path));
                    }
                    else if ((contentType & ContentType.CSS) != 0)
                    {
                        sb.Append(GenerateStyleTag(path));
                    }

                    begin += takeNum;

                }
            }
            else
            {
                foreach (string content in parameters)
                {
                    string path = string.Format("{0}{1}{2}/{3}?t={4}", cdnServer, packageDetail.Path, packageDetail.Value, content, packageDetail.Tag);
                    if ((contentType & ContentType.Javascript) != 0)
                    {
                        sb.Append(GenerateScriptTag(path));
                    }
                    else if ((contentType & ContentType.CSS) != 0)
                    {
                        sb.Append(GenerateStyleTag(path));
                    }
                }
            }
            return sb.ToString();
        }


        #endregion
    }

    [Flags]
    public enum ContentType : uint
    {
        Javascript = 0x01,
        CSS = 0x2

    }

    public class JSConfigOjb
    {
        public object ConfigKey { get; set; }
        public object ConfigValue { get; set; }
        public bool IsSerializedString { get; set; }
    }

    public class ContentDic
    {
        public ContentDicItem[] Items { get; set; }

        public ContentDic()
        {
            Items = new ContentDicItem[ContentMappingConfiguration.Current.Packages.Count];
            int curIndex = 0;
            foreach (ContentPackageElement package in ContentMappingConfiguration.Current.Packages)
            {
                Items[curIndex] = new ContentDicItem()
                {
                    Package = package,
                    Contents = null
                };
                ++curIndex;
            }
        }

        public void AddContent(string pkgName, string contentPath)
        {
            foreach (ContentDicItem item in Items)
            {
                if (item.Package.Name == pkgName)
                {
                    if (item.Contents != null)
                    {
                        if (!item.Contents.Contains(contentPath))
                        {
                            item.Contents.Add(contentPath);
                        }
                    }
                    else
                    {
                        item.Contents = new List<string> { contentPath };
                    }
                }
            }
        }

        public bool IsPkgExist(string pkgName)
        {
            bool result = false;
            foreach (ContentDicItem item in Items)
            {
                if (item.Package.Name == pkgName)
                    return true;
            }
            return result;
        }

        public ContentDicItem GetContentsWithPkg(string pkgName)
        {
            foreach (ContentDicItem item in Items)
            {
                if (item.Package.Name == pkgName)
                    return item;
            }
            return null;
        }

    }
    public class ContentDicItem
    {
        public ContentPackageElement Package { get; set; }
        public List<string> Contents { get; set; }
    }
}
